import { TourRelativeCommand, TourStartCommand, TourStepCommand, TourStopCommand } from "../command/tour.command"
import keyConst from "../constant/key.const"
import playerConst from "../constant/player.const"
import ApplicationData from "../data/ApplicationData"
import CameraData from "../data/CameraData"
import SettingsData from "../data/SettingsData"
import TourData from "../data/TourData"
import ViewmodeData from "../data/ViewmodeData"
import { WaitForPromiseGeneratorResult } from "../engine/EngineGenerators"
import EngineContext from "../EngineContext"
import { KeyState } from "../enum/hotKey.enum"
import { phaseMin } from "../enum/phase.enum"
import tourStateEnum from "../enum/tourState.enum"
import { TourTransitionList } from "../enum/tourTransition.enum"
import transitionTypeEnum from "../enum/transitionType.enum"
import viewmodeEnum from "../enum/viewmode.enum"
import { KeyEvent } from "../event/KeyEvent"
import { PointerButtonEvent } from "../event/PointerEvent"
import { ScrollEvent } from "../event/ScrollEvent"
import { TourStartError, TourTransitionError } from "../exception/tour.exception"
import { AppPhaseChangeMessage } from "../message/app.message"
import { TourEndedMessage, TourStartedMessage, TourStartTransitionMessage, TourSteppedMessage, TourStoppedMessage } from "../message/tour.message"
import n from "../packages/77037"
import SymbolList from "../SymbolList"
import TourTransitionFactory from "../transition/TourTransitionFactory"
import funcUtil from "../utils/func.util"
import { isPanOrMesh } from "../utils/viewmode.util"
import InputIniModule from "./InputIniModule"
import Module from "./Module"

export default class ToursControlsModule extends Module {
  getBurnsStyleForSnapshot: (e: any, t: any, i: any) => number
  canChangeTourLocation: () => any
  data: TourData
  cameraData: CameraData
  viewmodeData: ViewmodeData
  engine: EngineContext
  transitionFactory: TourTransitionFactory
  tourGenerator: () => Generator<WaitForPromiseGeneratorResult, void, unknown>
  constructor() {
    super(...arguments)
    this.name = "tours-controls"
    this.getBurnsStyleForSnapshot = (e, t, i) => {
      const s = e.getTourSnapshotSid(t),
        n = e.getHighlightDescription(s),
        r = e.getSnapshotCount()
      if (!n || !n.snapshot) return TourTransitionList.None
      const a = n.snapshot.metadata.cameraMode,
        h = t === r - 1,
        l = n.reelEntry && n.reelEntry.overrides && n.reelEntry.overrides.panDirection,
        d = a === viewmodeEnum.Dollhouse ? TourTransitionList.PanDollhouse : a === viewmodeEnum.Floorplan ? TourTransitionList.Zoom : TourTransitionList.Pan
      return TourTransitionFactory.useDelayTransition(this.settingsData, d, i)
        ? TourTransitionList.Delay
        : h && void 0 !== l
        ? d
        : h
        ? TourTransitionList.None
        : d
    }
    this.canChangeTourLocation = () => {
      const e = this.data.getTourState(),
        t = this.data.isTourTransitionActive(),
        i = this.cameraData.canTransition()
      return e === tourStateEnum.Inactive && !t && !(this.viewmodeData.transition && this.viewmodeData.transition.active) && i
    }
  }
  settingsData: SettingsData
  async init(_e, t: EngineContext) {
    this.engine = t
    this.cameraData = await t.market.waitForData(CameraData)
    this.viewmodeData = await t.market.waitForData(ViewmodeData)
    this.settingsData = await t.market.waitForData(SettingsData)
    this.data = await t.market.waitForData(TourData)
    t.getModuleBySymbol<InputIniModule>(SymbolList.INPUT).then(e => {
      e.registerHandler(PointerButtonEvent, () => {
        this.handleTourInputInterrupt()
      })
      e.registerHandler(ScrollEvent, () => {
        this.handleTourInputInterrupt()
      })
      e.registerHandler(KeyEvent, e => {
        e.state === KeyState.DOWN && this.handleTourInputInterrupt()
      })
    })
    this.transitionFactory = new TourTransitionFactory(t, this.data, this.cameraData, this.settingsData, this.viewmodeData)
    await this.transitionFactory.init()
    this.setupAutoPlay(t)
    this.bindings.push(t.commandBinder.addBinding(TourStartCommand, async e => this.startTour(e.index, e.steps, e.loop)))
    this.bindings.push(t.commandBinder.addBinding(TourStopCommand, async () => this.stopTour()))
    this.bindings.push(t.commandBinder.addBinding(TourStepCommand, async e => this.tourGoTo(e.index, e.instant ? transitionTypeEnum.Instant : void 0)))
    this.bindings.push(t.commandBinder.addBinding(TourRelativeCommand, async e => (e.forward ? this.tourGoNext(e.instant) : this.tourGoPrevious(e.instant))))
    this.bindings.push(
      t.subscribe(TourSteppedMessage, () => {
        this.data.setTourTransitionToIndex(-1)
      }),
      t.subscribe(TourStoppedMessage, () => {
        this.data.setTourTransitionToIndex(-1)
      })
    )
  }
  handleTourInputInterrupt() {
    this.data.getTourState() !== tourStateEnum.Inactive && this.stopTour()
  }
  canTourProceed(e, t, i) {
    const s = this.data.getSnapshotCount()
    if (0 === s || 0 === i || this.data.getTourState() !== tourStateEnum.Inactive) return !1
    if (void 0 !== t) {
      if (t < -1) return !1
      if (!0 !== e && t > s - 1) return !1
    }
    return !0
  }

  shouldTourContinue(SnapshotCount: number, playSnapshotIndex: number, playIndex: number, steps: number) {
    return void 0 !== steps ? playIndex < steps : playSnapshotIndex < SnapshotCount - 1
  }

  startTour(index?: number, steps?: number, loop?: boolean) {
    if (!this.canChangeTourLocation()) throw new TourStartError("Cannot start tour at this time, another transition is active")
    const isLooping = void 0 !== loop ? loop : this.data.isLooping()
    if (!this.canTourProceed(isLooping, index, steps)) return
    const SnapshotCount = this.data.getSnapshotCount()
    const SnapshotIndex = this.data.getTourCurrentSnapshotIndex()
    let playSnapshotIndex: number
    let playIndex = 0
    playSnapshotIndex = void 0 !== index ? index - 1 : SnapshotIndex >= SnapshotCount - 1 ? -1 : SnapshotIndex
    const tilingQuality = this.settingsData.tryGetProperty(keyConst.settings.tiling.quality, null)
    this.settingsData.setProperty(keyConst.settings.tiling.quality, null)
    const _this = this
    this.tourGenerator = function* () {
      for (; _this.shouldTourContinue(SnapshotCount, playSnapshotIndex, playIndex, steps); ) {
        const nextPlaySnapshotIndex = playSnapshotIndex + 1
        const TourTransition = _this.composeTourTransition(nextPlaySnapshotIndex)
        yield new WaitForPromiseGeneratorResult(TourTransition)
        playSnapshotIndex = nextPlaySnapshotIndex
        playSnapshotIndex === SnapshotCount - 1 && isLooping && (playSnapshotIndex = -1)
        playIndex++
      }
      _this.stopTour()
      _this.settingsData.setProperty(keyConst.settings.tiling.quality, tilingQuality)
    }
    this.engine.startGenerator(this.tourGenerator)
    this.data.setTourState(tourStateEnum.Active)
    this.data.tourEnded = false
    this.data.tourPlaying = true
    this.data.commit()
    this.engine.broadcast(new TourStartedMessage())
  }
  async composeTourTransition(nextIndex: number, setBurnsTransition?: number, setTransitionType?: number) {
    const sid = this.data.getTourSnapshotSid(nextIndex)
    const Highlight = this.data.getHighlightDescription(sid)
    if (!Highlight.snapshot) throw Error(`Highlight not found for reel index ${nextIndex}`)
    let TransitionType = this.settingsData.tryGetProperty(playerConst.TransitionType, transitionTypeEnum.Interpolate)
    const canCameraMode = Highlight.snapshot.metadata.cameraMode
    const DollhouseOrFloorplan = canCameraMode === viewmodeEnum.Dollhouse || canCameraMode === viewmodeEnum.Floorplan
    const PanOrMesh = isPanOrMesh(canCameraMode)
    const p = !this.viewmodeData.isInside() && PanOrMesh
    const g = this.viewmodeData.isInside() && DollhouseOrFloorplan
    const m = !this.viewmodeData.isInside() && DollhouseOrFloorplan
    const w = this.viewmodeData.isInside() && PanOrMesh
    let panAngle: number = null
    if (p || g || m) {
      TransitionType = transitionTypeEnum.Interpolate
    }
    if (w && 0 === nextIndex) {
      TransitionType = transitionTypeEnum.FadeToBlack
    }
    if (Highlight.reelEntry?.overrides) {
      void 0 !== Highlight.reelEntry.overrides.transitionType && (TransitionType = Highlight.reelEntry.overrides.transitionType)
      void 0 !== Highlight.reelEntry.overrides.panAngle && (panAngle = Highlight.reelEntry.overrides.panAngle)
    }
    if (setTransitionType !== undefined) {
      TransitionType = setTransitionType
    }
    this.data.setTourTransitionToIndex(nextIndex)
    this.engine.broadcast(new TourStartTransitionMessage())
    const FloorTransition = this.transitionFactory.getFloorTransition(nextIndex)
    this.data.useTransition(FloorTransition)
    await FloorTransition.promise
    if (this.data.getTourState() === tourStateEnum.StopScheduled) return
    const MainTransition = this.transitionFactory.getMainTransition(nextIndex, TransitionType)
    this.data.useTransition(MainTransition)
    await MainTransition.promise
    this.data.setTourCurrentSnapshotByIndex(nextIndex)
    this.engine.broadcast(new TourSteppedMessage(nextIndex))
    if (this.data.getTourState() === tourStateEnum.StopScheduled) {
      //停留动画
      const BurnsTransitionType = setBurnsTransition || this.getBurnsStyleForSnapshot(this.data, nextIndex, panAngle)
      const BurnsTransition = this.transitionFactory.getBurnsTransition(nextIndex, BurnsTransitionType)
      this.data.useTransition(BurnsTransition)
      await BurnsTransition.promise
    }
    //导览路径停留时间配置
    await funcUtil.waitRun(1000)
  }
  async stopTour() {
    if (this.data.getTourState() === tourStateEnum.Active) {
      this.data.setTourState(tourStateEnum.StopScheduled)
      this.data.tourPlaying = !1
      this.data.commit()
      await this.data.stopTourTransition()
      this.tourGenerator && this.engine.stopGenerator(this.tourGenerator)
      this.engine.broadcast(new TourStoppedMessage())
      if (this.data.getTourCurrentSnapshotIndex() === this.data.getSnapshotCount() - 1 && !this.data.isLooping()) {
        this.data.tourEnded = !0
        this.engine.broadcast(new TourEndedMessage())
      }
      this.data.setTourState(tourStateEnum.Inactive)
    }
  }
  async tourGoNext(e?: boolean) {
    let t = this.data.getTourCurrentSnapshotIndex() + 1
    t >= this.data.getSnapshotCount() && (t = 0)
    const i = e ? transitionTypeEnum.Instant : void 0
    return this.tourGoTo(t, i)
  }
  async tourGoPrevious(e?: boolean) {
    let t = this.data.getTourCurrentSnapshotIndex()
    t < 0 && (t = 0)
    let i = t - 1
    i < 0 && (i = this.data.getSnapshotCount() - 1)
    const s = e ? transitionTypeEnum.Instant : void 0
    return this.tourGoTo(i, s)
  }
  async tourGoTo(e: number, transitionType = transitionTypeEnum.FadeToBlack) {
    if (!this.canChangeTourLocation()) throw new TourTransitionError("Cannot change tour location at this time, another transition is active")
    if (this.viewmodeData.transition && this.viewmodeData.transition.active)
      throw new TourTransitionError("Cannot go to tour location during viewmode transition")
    if (this.data.getTourState() !== tourStateEnum.Inactive) throw new TourTransitionError("Cannot jump to tour location while tour is active")
    try {
      this.data.setTourState(tourStateEnum.Active), await this.composeTourTransition(e, TourTransitionList.None, transitionType)
    } catch (e) {
      this.log.error(e)
    } finally {
      this.data.setTourState(tourStateEnum.Inactive)
    }
  }
  setupAutoPlay(e: EngineContext) {
    const t = 1000 * n.Xd.autoStartDelay
    const i = () => {
      let e = !0
      const i = this.cameraData.pose.onChanged(() => {
        ;(e = !1), i.cancel()
      })
      setTimeout(() => {
        e && this.startTour(), i.cancel()
      }, t)
    }
    if (t >= 0) {
      const t = e.market.tryGetData(ApplicationData)
      if (t && t.phase === phaseMin.PLAYING) i()
      else {
        const t = s => {
          s.phase === phaseMin.PLAYING && (i(), e.unsubscribe(AppPhaseChangeMessage, t))
        }
        e.subscribe(AppPhaseChangeMessage, t)
      }
    }
  }
}
